﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework;

namespace Skugo.Shared
{
	/// <summary>
    /// Simple extensions that are made to any class.
	/// </summary>
	public static class Extensions
	{
        /// <summary>
        /// Turns any single object into an enumerable object.
        /// </summary>
        /// <typeparam name="T">The type of the enumerable object.</typeparam>
        /// <param name="item">The item we'd like to convert into an enumerable type.</param>
        /// <returns>Returns the enumerable object containing the single item.</returns>
        public static IEnumerable<T> AsEnumerable<T>(this T item)
        {
            yield return item;
        }

		/// <summary>
        /// Read an ascii string from a binary reader.
		/// </summary>
		/// <param name="reader">The reader which has the string encoded.</param>
		/// <returns>Returns the string that was read.</returns>
		public static String ReadAsciiString(this BinaryReader reader)
		{
			// Read the string size, and then read the data
			UInt32 stringSize = reader.ReadUInt32();
			Byte[] stringData = reader.ReadBytes((Int32)stringSize);

			// Convert the byte array into a valid string
            return System.Text.ASCIIEncoding.ASCII.GetString(stringData);
		}

        /// <summary>
        /// Write a string as an ascii string to a binary writer.
        /// </summary>
        /// <param name="writer">The binary writer that we want to write the string to.</param>
        /// <param name="toWrite">The string to be written.</param>
        public static void WriteAsciiString(this BinaryWriter writer, String toWrite)
        {
            // Convert the string into an ascii byte array
            Byte[] stringData = System.Text.ASCIIEncoding.ASCII.GetBytes(toWrite);

            // Write the string size, then write the data
            writer.Write((UInt32)toWrite.Length);
            writer.Write(stringData);
        }

        /// <summary>
        /// Read a Matrix from a binary reader.
        /// </summary>
        /// <param name="reader">The reader which has the value encoded.</param>
        /// <returns>Returns the value that was read.</returns>
        public static Matrix ReadMatrix(this BinaryReader reader)
        {
            Matrix result;
            result.M11 = reader.ReadSingle();
            result.M12 = reader.ReadSingle();
            result.M13 = reader.ReadSingle();
            result.M14 = reader.ReadSingle();
            result.M21 = reader.ReadSingle();
            result.M22 = reader.ReadSingle();
            result.M23 = reader.ReadSingle();
            result.M24 = reader.ReadSingle();
            result.M31 = reader.ReadSingle();
            result.M32 = reader.ReadSingle();
            result.M33 = reader.ReadSingle();
            result.M34 = reader.ReadSingle();
            result.M41 = reader.ReadSingle();
            result.M42 = reader.ReadSingle();
            result.M43 = reader.ReadSingle();
            result.M44 = reader.ReadSingle();
            return result;
        }

        /// <summary>
        /// Write a Matrix to a binary writer.
        /// </summary>
        /// <param name="writer">The binary writer that we want to write the value to.</param>
        /// <param name="toWrite">The value to be written.</param>
        public static void WriteMatrix(this BinaryWriter writer, Matrix toWrite)
        {
            writer.Write(toWrite.M11);
            writer.Write(toWrite.M12);
            writer.Write(toWrite.M13);
            writer.Write(toWrite.M14);
            writer.Write(toWrite.M21);
            writer.Write(toWrite.M22);
            writer.Write(toWrite.M23);
            writer.Write(toWrite.M24);
            writer.Write(toWrite.M31);
            writer.Write(toWrite.M32);
            writer.Write(toWrite.M33);
            writer.Write(toWrite.M34);
            writer.Write(toWrite.M41);
            writer.Write(toWrite.M42);
            writer.Write(toWrite.M43);
            writer.Write(toWrite.M44);
        }

        /// <summary>
        /// Read a Vector2 from a binary reader.
        /// </summary>
        /// <param name="reader">The reader which has the value encoded.</param>
        /// <returns>Returns the value that was read.</returns>
        public static Vector2 ReadVector2(this BinaryReader reader)
        {
            Vector2 result;
            result.X = reader.ReadSingle();
            result.Y = reader.ReadSingle();
            return result;
        }

        /// <summary>
        /// Write a Vector2 to a binary writer.
        /// </summary>
        /// <param name="writer">The binary writer that we want to write the value to.</param>
        /// <param name="toWrite">The value to be written.</param>
        public static void WriteVector2(this BinaryWriter writer, Vector2 toWrite)
        {
            writer.Write(toWrite.X);
            writer.Write(toWrite.Y);
        }

        /// <summary>
        /// Read a Vector3 from a binary reader.
        /// </summary>
        /// <param name="reader">The reader which has the value encoded.</param>
        /// <returns>Returns the value that was read.</returns>
        public static Vector3 ReadVector3(this BinaryReader reader)
        {
            Vector3 result;
            result.X = reader.ReadSingle();
            result.Y = reader.ReadSingle();
            result.Z = reader.ReadSingle();
            return result;
        }

        /// <summary>
        /// Write a Vector3 to a binary writer.
        /// </summary>
        /// <param name="writer">The binary writer that we want to write the value to.</param>
        /// <param name="toWrite">The value to be written.</param>
        public static void WriteVector3(this BinaryWriter writer, Vector3 toWrite)
        {
            writer.Write(toWrite.X);
            writer.Write(toWrite.Y);
            writer.Write(toWrite.Z);
        }

        /// <summary>
        /// Read a Vector4 from a binary reader.
        /// </summary>
        /// <param name="reader">The reader which has the value encoded.</param>
        /// <returns>Returns the value that was read.</returns>
        public static Vector4 ReadVector4(this BinaryReader reader)
        {
            Vector4 result;
            result.X = reader.ReadSingle();
            result.Y = reader.ReadSingle();
            result.Z = reader.ReadSingle();
            result.W = reader.ReadSingle();
            return result;
        }

        /// <summary>
        /// Write a Vector4 to a binary writer.
        /// </summary>
        /// <param name="writer">The binary writer that we want to write the value to.</param>
        /// <param name="toWrite">The value to be written.</param>
        public static void WriteVector4(this BinaryWriter writer, Vector4 toWrite)
        {
            writer.Write(toWrite.X);
            writer.Write(toWrite.Y);
            writer.Write(toWrite.Z);
            writer.Write(toWrite.W);
        }

        /// <summary>
        /// Read a Quaternion from a binary reader.
        /// </summary>
        /// <param name="reader">The reader which has the value encoded.</param>
        /// <returns>Returns the value that was read.</returns>
        public static Quaternion ReadQuaternion(this BinaryReader reader)
        {
            Quaternion result;
            result.X = reader.ReadSingle();
            result.Y = reader.ReadSingle();
            result.Z = reader.ReadSingle();
            result.W = reader.ReadSingle();
            return result;
        }

        /// <summary>
        /// Write a Quaternion to a binary writer.
        /// </summary>
        /// <param name="writer">The binary writer that we want to write the value to.</param>
        /// <param name="toWrite">The value to be written.</param>
        public static void WriteQuaternion(this BinaryWriter writer, Quaternion toWrite)
        {
            writer.Write(toWrite.X);
            writer.Write(toWrite.Y);
            writer.Write(toWrite.Z);
            writer.Write(toWrite.W);
        }

        /// <summary>
        /// Get a binary reader from a data source.
        /// </summary>
        /// <param name="data">The source of data.</param>
        /// <returns>Returns a reader that will read the data.</returns>
        public static BinaryReader ToBinaryReader(this Byte[] data)
        {
            // Create a memory stream for the data
            MemoryStream stream = new MemoryStream(data);

            // Create a binary reader to parse the memory stream
            return new BinaryReader(stream);
        }

        public static Value FindValue<Key, Value>(this Dictionary<Key, Value> map, Key lookFor, Value defaultValue)
        {
            Value result;
            if (map.TryGetValue(lookFor, out result))
            {
                return result;
            }

            return defaultValue;
        }

        public static Value FindValue<Key, Value>(this Dictionary<Key, Value> map, Key lookFor) where Value : class
        {
            return map.FindValue(lookFor, null);
        }

		/// <summary>
        /// Check if one type inherits from another.
		/// </summary>
		/// <param name="derivedType">The derived type which we are checking if it inherits from the base type.</param>
		/// <param name="baseType">The base type who the derived type may inherit from.</param>
		/// <returns>Returns true if the derived type does inherit from the base type, false otherwise.</returns>
		public static Boolean InheritsFrom(this Type derivedType, Type baseType)
		{
			return baseType.IsAssignableFrom(derivedType) && derivedType != baseType;
		}

		/// <summary>
        /// How we structure the references.
		/// </summary>
		public enum ReferenceForm
		{
			AsPointer = 0,
			AsValue = 1
		}

		// Write a value reference using a binary writer
        public static void WriteReference(this BinaryWriter writer, Boolean value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, Byte value)
		{
			// Always write the references as a value
            writer.Write((Byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, Char value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, Decimal value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, Double value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, Single value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, Int32 value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, Int64 value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, SByte value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, Int16 value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, String value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, UInt32 value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, UInt64 value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

		// Write a value reference using a binary writer
		public static void WriteReference(this BinaryWriter writer, UInt16 value)
		{
			// Always write the references as a value
			writer.Write((byte)ReferenceForm.AsValue);

			// Write the actual value
			writer.Write(value);
		}

        // An efficient hash algorithm for c-strings strings
        public static Int64 HashString(String str)
        {
            // Store the length as a signed 64 bit int
            Int64 length = str.Length;

            // Start the hash out at the length of the string
            Int64 hash = length;

            // Compute a step so that way if the string is long, we don't iterate over single every character
            Int64 step = (length >> 5) + 1;

            // Walk backwards through the string
            for (Int64 i = length; i >= step; i -= step)
            {
                // Get the current character
                Byte current = (Byte)str[(Int32)i - 1];

                // Randomize the bits of the hash, while using the string data
                hash = hash ^ ((hash << 5) + (hash >> 2) + current);
            }

            // Return the computed hash
            return hash;
        }
	}
}
